package com.inepex.inechart.shared.linechart;

import java.util.ArrayList;
import java.util.List;
import java.util.TreeMap;

import com.inepex.inechart.shared.CartesianChartModule;
import com.inepex.inechart.shared.axes.AxesDefault;
import com.inepex.inechart.shared.properties.Color;
import com.inepex.inegraphics.shared.GraphicalObjectContainer;
import com.inepex.inegraphics.shared.gobjects.GraphicalObject;
import com.inepex.inegraphics.shared.gobjects.Path;

public class LineChartDefault extends CartesianChartModule implements LineChart {

	protected LineChartToGO lineChartToGO;
	protected LineChartModelAssist lineChartModelAssist;
	
	// model fields
	protected ArrayList<Curve> curves;
	protected int highestZIndex = 1;
	protected int zIndexDiffBetweenCurves = 3;

	protected TreeMap<Curve, Path> visiblePathPerCurve;
	protected TreeMap<Curve, Path> fillPathPerCurve;

	/**
	 * all {@link GraphicalObject}s related to line chart per {@link Curve}
	 */
	protected TreeMap<Curve, GraphicalObjectContainer> lineChartGOsPerCurve;

	/**
	 * all {@link GraphicalObject}s related to point chart per {@link Curve}
	 */
	protected TreeMap<Curve, GraphicalObjectContainer> pointChartGOsPerCurve;

	public LineChartDefault(AxesDefault axes) {
		super(axes);
		lineChartToGO = new LineChartToGO(cartesianAssist);
		lineChartModelAssist = new LineChartModelAssist(cartesianAssist);
		curves = new ArrayList<Curve>();
		visiblePathPerCurve = new TreeMap<Curve, Path>();
		fillPathPerCurve = new TreeMap<Curve, Path>();
		lineChartGOsPerCurve = new TreeMap<Curve, GraphicalObjectContainer>();
		pointChartGOsPerCurve = new TreeMap<Curve, GraphicalObjectContainer>();

		// defaults
		autoScaleViewportHorizontal = true;
		setPointFilter(new PointFilter());
	}

	@Override
	public void addCurve(Curve curve) {
		if (curve == null){
			return;
		}
		curves.add(curve);
		if (curve.zIndex == Integer.MIN_VALUE){
			highestZIndex += zIndexDiffBetweenCurves;
			curve.zIndex = highestZIndex;
		}
		else if (curve.zIndex > highestZIndex){
			highestZIndex = curve.zIndex;
		}
	}

	@Override
	public void removeCurve(Curve curve) {
		if (curve == null)
			return;
		removeAllGORelatedToCurve(curve);
		pointChartGOsPerCurve.remove(curve);
		lineChartGOsPerCurve.remove(curve);
		curves.remove(curve);
	}

	public void preUpdateModule() {
		if (curves == null || curves.size() == 0)
			return;
		if (autoScaleViewportHorizontal || autoScaleViewportVertical) {
			double yMin = Double.MAX_VALUE;
			double yMax = -Double.MAX_VALUE;
			double xMin = Double.MAX_VALUE;
			double xMax = -Double.MAX_VALUE;

			for (Curve c : curves) {
				if (c.dataSet.getKeyMax().getValuePair()[0] > xMax)
					xMax = c.dataSet.getKeyMax().getValuePair()[0];
				if (c.dataSet.getValueMax().getValuePair()[1] > yMax)
					yMax = c.dataSet.getValueMax().getValuePair()[1];
				if (c.dataSet.getKeyMin().getValuePair()[0] < xMin)
					xMin = c.dataSet.getKeyMin().getValuePair()[0];
				if (c.dataSet.getValueMin().getValuePair()[1] < yMin)
					yMin = c.dataSet.getValueMin().getValuePair()[1];
			}
			if(yMin == yMax){
				yMax++;
				yMin--;
			}
			if(xMin == xMax){
				xMax++;
				xMin--;
			}
			if(autoScaleViewportHorizontal && cartesianAssist.getxAxis().getModulToAlign() == this){
				cartesianAssist.getxAxis().setMax(xMax);
				cartesianAssist.getxAxis().setMin(xMin);
			}
			if(autoScaleViewportVertical && cartesianAssist.getyAxis().getModulToAlign() == this){
				cartesianAssist.getyAxis().setMax(yMax);
				cartesianAssist.getyAxis().setMin(yMin);	
			}
			autoScaleViewportHorizontal = false;
			autoScaleViewportVertical = false;
		}
		super.preUpdateModule();
	}

	@Override
	public GraphicalObjectContainer update() {
		GraphicalObjectContainer graphicalObjectContainer = new GraphicalObjectContainer();
		if(!visible){
			return graphicalObjectContainer;
		}
		if (autoScaleViewportHorizontal || autoScaleViewportVertical) {
			preUpdateModule();
		}
		if (curves == null || curves.size() == 0){
			return graphicalObjectContainer;
		}
		//do model to canvas calculations
		for (Curve curve : curves) {
			if(curve.isVisible()){
				updateCurveModel(curve);
			}
		}
		//remove previous and create new GOs
		for (Curve curve : curves) {
			if(curve.isVisible()){
				removeAndCreateCurveGOs(curve);
				if(lineChartGOsPerCurve.containsKey(curve))
					graphicalObjectContainer.add(lineChartGOsPerCurve.get(curve));
				if(pointChartGOsPerCurve.containsKey(curve))
					graphicalObjectContainer.add(pointChartGOsPerCurve.get(curve));
			}
		}
		return graphicalObjectContainer;
	}

	protected void updateCurveModel(Curve curve){
		lineChartModelAssist.calculatePointsForCurve(curve);
		if(curve.hasLine){
			Path path = lineChartToGO.createStrokePathForCurve(curve, cartesianAssist.getLeftPadding(), cartesianAssist.getRightEnd());
			if(path != null){
				visiblePathPerCurve.put(curve, path);
			}
		}
		if(shouldCalculateFillPathForCurve(curve)){
			Path path = lineChartToGO.createFillPathForCurve(curve, cartesianAssist.getLeftPadding(), cartesianAssist.getRightEnd());
			if(path != null){
				fillPathPerCurve.put(curve, path);
			}
		}
	}

	protected void removeAndCreateCurveGOs(Curve curve){
		removeAllGORelatedToCurve(curve);
		if(curve.hasLine){
			lineChartGOsPerCurve.put(curve, lineChartToGO.createLineChartGOs(curve, visiblePathPerCurve, fillPathPerCurve.get(curve)));
		}
		if(curve.hasPoint){
			pointChartGOsPerCurve.put(curve, lineChartToGO.createPointChartGOs(curve));
		}
	}

	public void updateCurve(Curve curve){
		updateCurveModel(curve);
		removeAndCreateCurveGOs(curve);
	}

	protected boolean shouldCalculateFillPathForCurve(Curve curve){
		if( curve.autoFill  ||
				(curve.toCurveFills != null && curve.toCurveFills.size() > 0) || 
				(curve.toYFills != null && curve.toYFills.size() > 0)){
			return true;
		}
		else{
			for(Curve c : curves){
				if(c == curve)
					continue;
				if(c.toCurveFills != null && c.toCurveFills.containsKey(curve))
					return true;
			}
		}
		return false;
	}

	/**
	 * Removes all {@link GraphicalObject} from the related {@link GraphicalObjectContainer} based on {@link #lineChartGOsPerCurve} and
	 * {@link #pointChartGOsPerCurve} containers.
	 * 
	 * @param curve
	 */
	protected void removeAllGORelatedToCurve(Curve curve) {
		lineChartGOsPerCurve.remove(curve);
		pointChartGOsPerCurve.remove(curve);
	}

	@Override
	public TreeMap<String, Color> getLegendEntries() {
		if(legendEntries == null){
			TreeMap<String, Color> entries = new TreeMap<String, Color>();
			for(Curve c : curves){
				entries.put(c.getDataSet().getTitle(),
						c.getLineProperties() != null && c.getLineProperties().getLineColor() != null ?
								c.getLineProperties().getLineColor() :
									c.getDataSet().getColor());
			}

			return entries;
		}
		else{
			return legendEntries;
		}
	}

	@Override
	public PointFilter getPointFilter() {
		return lineChartModelAssist.getPointFilter();
	}

	@Override
	public void setPointFilter(PointFilter pointFilter) {
		lineChartModelAssist.setPointFilter(pointFilter);
	}

	/**
	 * @return the Curves
	 */
	@Override
	public final List<Curve> getCurves() {
		return curves;
	}

}